/* Copyright (c) 2011-2013 ISP RAS, Moscow. For more information please write to

      mermakov@ispras.ru
      svartanov@ispras.ru

   Licensed under the Apache License, Version 2.0 (the "License");
   you may not use this file except in compliance with the License.
   You may obtain a copy of the License at

       http://www.apache.org/licenses/LICENSE-2.0

   Unless required by applicable law or agreed to in writing, software
   distributed under the License is distributed on an "AS IS" BASIS,
   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   See the License for the specific language governing permissions and
   limitations under the License.
*/

package ru.ispras.coffeemachine.instrument;

import java.io.IOException;
import java.util.Set;

import ru.ispras.coffeemachine.util.Logger;
import ru.ispras.coffeemachine.util.Options;
import ru.ispras.coffeemachine.util.Util;

/**
 * Class file for instrumentation
 *
 * @author Mikhail Ermakov (mermakov@ispras.ru)
 * @version 1.2.0
 */
public class Classfile {

	private final String name;
	private final String jar;
	private final String trimmedJar;
	private Set<String> methods;

	private final Mokka mokka;

	/**
	 * Create a classfile entry from a specified Jar file (or standalone Java file)
	 * with all methods marked for instrumentation
	 *
	 * @param name fully specified class name (e.g. {@link java.lang.String java/lang/String})
	 * @param jar path to Jar file or <code>null</code> for standalone Java files
	 * @throws ClassNotFoundException
	 */
	public Classfile(String name, String jar) throws ClassNotFoundException {

		this(name, jar, null);
		methods = mokka.getMethodNames();
		mokka.setMethods(methods);
	}

	/**
	 * Create a classfile entry from a specified Jar file (or standalone Java file)
	 * with specified set of methods marked for instrumentation
	 *
	 * @param name fully specified class name (e.g. {@link java.lang.String java/lang/String})
	 * @param jar path to Jar file or <code>null</code> for standalone Java files
	 * @param methods a set of fully specified methods to instrument (e.g. {@link java.lang.String#charAt(int) charAt(I)C})
	 * @throws ClassNotFoundException
	 */
	public Classfile(String name, String jar, Set<String> methods) throws ClassNotFoundException {

		this.name = name;
		this.jar = jar;
		if (jar != null) {
			int delimIndex = jar.lastIndexOf('/');
			if (delimIndex != -1) {
				trimmedJar = jar.substring(delimIndex + 1);
			} else {
				trimmedJar = jar;
			}
		} else {
			trimmedJar = null;
		}
		this.methods = methods;
		mokka = new Mokka(name, methods);
	}

	@Override
	public boolean equals(Object object) {

		if (object instanceof Classfile) {
			if (name == null) {
				return ((Classfile)object).name == null;
			}
			return name.equals(((Classfile)object).name);

		}
		return false;
	}

	@Override
	public int hashCode() {

		if (name == null) {
			return 0;
		}
		return name.hashCode();
	}

	/**
	 * Returns Jar file name
	 *
	 * @return Jar file name or <code>null</code> if classfile is stored in standalone Java file
	 */
	public String getJar() {

		return jar;
	}

	/**
	 * Returns Jar file name stripped of path prefix (e.g. <code>tmp.jar</code> for <code>/tmp/tmp.jar</code>)
	 */
	public String getTrimmedJar() {

		return trimmedJar;
	}

	/**
	 * Returns a set of methods marked for instrumentation
	 *
	 * @return a set of methods marked for instrumentation
	 */
	public Set<String> getMethods() {

		return methods;
	}

	/**
	 * Returns active {@link Mokka} instance
	 *
	 * @return active {@link Mokka} instance
	 */
	protected Mokka getMokka() {

		return mokka;
	}

	/**
	 * Returns class name
	 *
	 * @return class name
	 */
	public String getName() {

		return name;
	}

	/**
	 * Marks a set of methods for instrumentation
	 *
	 * @param methods a set of fully specified methods (e.g. {@link java.lang.String#charAt(int) charAt(I)C})
	 */
	public void setMethods(Set<String> methods) {

		this.methods = methods;
	}

	/**
	 * Updates contents of Jar file (if specified) or Java file (otherwise)
	 */
	public void update() {

		if ((name == null)) {
			return;
		}
		try {
			mokka.updateClass();
			if (jar == null) {
				return;
			}
			Util.copyFile(jar, Options.instrumentationDir + trimmedJar);
		} catch (IOException e) {
			Logger.error("Cannot update class " + name + ": " + e.getLocalizedMessage());
		}
	}



	/**
	 * Marks a method for instrumentation
	 *
	 * @param name fully specified method (e.g.  {@link java.lang.String#charAt(int) charAt(I)C})
	 */
	public void addMethod(String name) {

		if (methods != null) {
			methods.add(name);
		}
	}
}
